<?php
namespace App\GameService\games;

use App\Common\GameCommon;
use App\Utility\BaseEnum;
use App\Common\AuthCommon;
use Swoole\Timer;
use App\Utility\RedisTools;
use App\Common\BaseComm;
use App\Enum\CardEnum;
use App\Enum\SysEnum;
use App\Common\PlayCommon;

/**
 * 牛牛游戏
 *
 * @author Administrator
 */
class PlayNiuNiu extends BasePlay
{
    
    public function __construct()
    {
        //TODO 配置时间:时间问题
        $this-> gtimeConfig = array(
            "quangzhuang_long" => 8,    //抢庄时长
            "waitbet_long" => 5,        //等待下注
            "playbet_long" => 5,        //下注时长
            "waitcard_long" => 1,       //下注完成完成，等待发牌时长
            "waitdeal_long" => 10,       //发牌完成完成，等待结算时长
        );
    }

    /**
     * TODO 看牌提前发三张牌
     *
     * @param array $playFds
     * @param array $useCards
     */
    protected function PushFrontThreeCards($playFds, $useCards = FALSE)
    {
        if (! $useCards || empty($useCards))
            return false; // 推送每个位置的牌值；
        foreach ($useCards as $onecards) {
            $seatid = isset($onecards["seatNo"]) ? intval($onecards["seatNo"]) : - 1; // 座位号
            $cards = isset($onecards["cards"]) ? $onecards["cards"] : FALSE;
            if (! empty($cards) && sizeof($cards) > 3 && $seatid >= 0 && isset($playFds[$seatid])) {
                $oneCard = [
                    "seatNo" => $seatid,
                    "cards" => array_slice($cards, 0, 3),
                    "step" => 0
                ]; // 只取前三张牌
                AuthCommon::pushFrontWeb($playFds[$seatid], json_encode([
                    'm' => BaseEnum::XYM_0X17,
                    "data" => $oneCard
                ])); // 推送相同内容
            }
        }
        return true;
    }

    /**
     * TODO 最高抢庄倍数计算
     * @param array $playPoints
     * @param number $roomEvery
     * @param number $roomMaxBet
     * @return boolean|number[]
     */
    protected function countPaiTimes($playPoints,$roomEvery = 1,$roomMaxBet = 15) {
        $countList = [];
        $maxUserQZList = [];
        $maxQzTimes = 3;  
        $maxCardTimes = 4;
        
        //TODO 计算最高倍数； 牌型 :4倍  *  抢庄最高3倍* 底注,    不高于房间最大下注 15倍； 
        if(!$playPoints || $roomEvery <= 0) return FALSE;
        foreach ($playPoints as $vsid => $onepoot){
            $maxPbt = floatval($onepoot / ($roomEvery * $maxCardTimes * $maxQzTimes ));
            if($maxPbt > 0){
                $countList[$vsid] = ($maxPbt > $roomMaxBet) ? $roomMaxBet : floor($maxPbt) ; //取玩家最高下注倍数
            }
        }
        
        //取最小值：minBet倍数；
        $playMinBets = !empty($countList) ? min($countList) : $roomMaxBet ;
        if ($playMinBets <= 0 || $playMinBets > $roomMaxBet) {
            $playMinBets = $roomMaxBet;
        }
        
        //TODO 计算每个玩家的最高抢庄倍数
        foreach ($playPoints as $osid => $onepoot){
            $maxQzBt  = round($onepoot / ($roomEvery * $playMinBets * $maxCardTimes ));   //最高抢庄倍数  = 余额分 / (房间底分 * 最高牌型14倍 * 玩家最小下注倍数)
            if (isset($maxQzBt) && intval($maxQzBt) > 0) {
                $maxQzBt = ($maxQzBt > $maxQzTimes) ? $maxQzTimes : $maxQzBt;
                $maxUserQZList[$osid] = $maxQzBt;
            }
        }
        //TODO 最高抢庄倍数计算
        BaseEnum::Logger("计算最高抢庄倍数:(countPaiTimes) 计算每个玩家最大下注倍数(最小值：{$playMinBets})，-->". json_encode($maxUserQZList), 'user-login');
        return $maxUserQZList;
    }
    
    
    /**
     * TODO开始游戏入口；
     * TODO必须实现这个方法；
     * TODO游戏开始逻辑
     */
    public function asycStartGame($gameNo, $gameRId = FALSE, $fjInfo = FALSE, &$commonLoopTimer = 0)
    {
        $gameId = isset($fjInfo['gameId']) ? intval($fjInfo['gameId']) : 0;
        $roomEvery = isset($fjInfo['everyPoints']) ? floatval($fjInfo['everyPoints']) : 0;
        $maxBet = isset($fjInfo['maxMultiple']) ? intval($fjInfo['maxMultiple']) : 15;
        $gameAllData = BaseComm::distMemoryRooms($gameNo, FALSE); // 内存数据；
        $fjSta = isset($gameAllData['fjStatus']) ? intval($gameAllData['fjStatus']) : 0;
        $gamePlays = isset($gameAllData['lists']) ? $gameAllData['lists'] : FALSE; // 玩家列表；
        $playFds = ! empty($gamePlays) ? array_column($gamePlays, "fd", "seatNo") : false; // 座位列表
        $playPoints = ! empty($gamePlays) ? array_column($gamePlays, "points", "seatNo") : false; // 座位列表
        
        $realUserId = FALSE; //真实玩家ID及座位编号
        
        //TODO 获取机器人信息
        $allRobotUsers = PlayCommon::getGameRobotsUser($gameId,$gameNo , $playFds,$realUserId);
        BaseEnum::Logger("游戏开始->{$gameId} , {$gameNo} 游戏数据 :Games-(PlayNiuNiu)----> 真实玩家ID-->>>" . json_encode($realUserId), 'user-login');

        //TODO 刚开始就给玩家生成牌的数据；
        $useCards = GameCommon::createCardList($gameId, $gameNo, ["roomId"=>$gameRId,"CellUserIds"=> $realUserId]);         // CellUserIds 有效玩家ID及座位号对应关系
        BaseEnum::Logger("游戏开始生成牌:Games-(PlayNiuNiu)-{$gameId} 再计算计算谁是[{$gameNo}]庄主---->>>" . json_encode($useCards), 'user-login'); // 所有玩家FD
        
        if ($gameId === 201) {
            $this->PushFrontThreeCards($playFds, $useCards);    // 看牌抢庄牛 === > 发3张牌（前三张）；
        }
        
        $Parms = [
            "gameNo" => $gameNo,
            "clientIds" => $playFds,
            "status" => $fjSta
        ];
        
        //TODO 每位玩家都按最大牌型倍数，最大抢庄倍数，计算出每位玩家的最大下注倍数===> 计算每位玩家的抢庄倍数
        $maxQzTimes = $this->countPaiTimes($playPoints,$roomEvery,$maxBet);
        
        //TODO 开启机器人自动抢庄
        $this->startRobotDoQiangz($gameId, $gameNo,$allRobotUsers,$playFds);

        //TODO 抢庄====倒计时10秒钟
        $qzLong = $this->getConfigTimeLong('quangzhuang_long') ?? 5;
        $this->asycTimeStart($qzLong, $gameNo, $Parms, function () use ($gameNo, $gameRId, $maxQzTimes, $fjInfo, $useCards) {
            $error = '';
            $notQzUser = FALSE;
            $gameId = isset($fjInfo['gameId']) ? intval($fjInfo['gameId']) : 0;
            if (! $fjInfo) {
                $fjInfo = BaseComm::GameRoomDetail($gameRId);
            }
            
            $gameData = BaseComm::distMemoryRooms($gameNo, FALSE); // 内存数据；
            $firstStamp = isset($gameData['create']) ? intval($gameData['create']) : 0;
            $gamePlays = isset($gameData['lists']) ? $gameData['lists'] : FALSE; // 玩家列表；
            $playFds = ! empty($gamePlays) ? array_column($gamePlays, "fd", "seatNo") : false; // 座位列表
            BaseEnum::Logger("选庄家定时器：Games-(PlayNiuNiu)->  ，计算谁是[{$gameNo}]庄家----> 最高抢庄倍数：" . json_encode($maxQzTimes), 'user-login'); // 所有玩家FD
            
            //TODO 计算谁是庄家
            $zjUser = GameCommon::countRoomMaimZhuang($gameNo, $gamePlays, $notQzUser,$maxQzTimes);
            $zjUid = isset($zjUser['uid']) ? intval($zjUser['uid']) : 0; // 抢到倍数大于0
            $zjSeatid = isset($zjUser['seatNo']) ? intval($zjUser['seatNo']) : - 1; // 抢到倍数大于0
            $zjMuts = isset($zjUser['mutdotts']) ? intval($zjUser['mutdotts']) : 0; // 抢到倍数大于0
            $zjPoints = isset($zjUser['points']) ? floatval($zjUser['points']) : 0; // 抢到倍数大于0
            BaseEnum::Logger("抢庄统计：Games-(PlayNiuNiu)-获取庄家：".json_encode($zjUser) . "\n哪些用户没有抢庄 ===>" . json_encode($notQzUser), 'user-login');
            if (!empty($notQzUser)) {
                foreach ($notQzUser as $onenotxj) {
                    $pushV = [ 'm' => BaseEnum::XYM_0X18, "data" => $onenotxj];
                    AuthCommon::pushFrontWeb($playFds, json_encode($pushV));        //推送不抢庄指令并且
                }
            }
            if (isset($zjUser) && $zjUid > 0 && isset($zjUser['fd']) && intval($zjUser['fd']) > 0 && ! empty($playFds) && $zjMuts > 0 && $zjPoints > 0) { // 抢zhuang 成功
                $totalNum = sizeof($playFds); // 通知给所有玩家，谁是庄家；
                //TODO 最大下注倍数
                $showList = array(
                    "seatNo" => $zjSeatid,
                    "mutdotts" => $zjMuts,
                    "max_mutle" => 10,
                    "nums" => $totalNum
                ); 
                //TODO 抢zhuang 成功； 计算每个用户最大倍数（要等玩家匹配成功才能计算的）；
                $maxBet_Mult = CardEnum::countDeskMaxMutle($totalNum, $zjMuts, $zjPoints, $fjInfo);
                $showList["max_mutle"] = ($maxBet_Mult > 0) ? $maxBet_Mult : rand(5, 8);
                //TODO 游戏记录写DB数据; ======> 记录玩家中谁是庄家及倍数；
                $savedata = array(
                    "ptype" => 1,
                    "userId" => $zjUid,
                    "data" => $zjMuts
                );
                $maxut = GameCommon::updateRoomsPInfo($gameNo, $savedata, $gamePlays, $error);

                //TODO 抢成功才会到下一步
                if (isset($maxut) && $maxut === true) {
                    RedisTools::Pool_hDel(SysEnum::ONLINE_BDATA_KEY,$gameNo);    //抢庄数据处理清掉缓存里值                    
                    // 推送到所有FD，庄家的座位号；[XYM_0X19 --抢庄成功 ]
                    BaseEnum::Logger("抢庄成功：Games-(PlayNiuNiu)-恭喜用户 UID={$zjUid} 抢庄(QZ：{$zjMuts}倍)成功---->>> SUCCESS !!!!" . $gameNo, 'user-login');
                    
                    
                    //TODO 获取起始时间间隔
                    $countStamp = BaseEnum::getNowTimeLong($firstStamp);
                    BaseComm::gameLogger($gameNo,"开局第{$countStamp}秒  , 定庄完成, {$zjSeatid} 号位为庄家 ({$zjMuts}倍),最高下注倍数：" . $showList["max_mutle"]);            //TODO 对局开始日志
                    $zjUserInfo = array(
                        "uid" => $zjUid,
                        "seatNo" => $zjSeatid,
                        "bet_mutle" => $zjMuts,
                        "main_flag" => 1
                    );
                    
                    //TODO 启动下一个定时器；定庄和 ======> 闲家选倍数；
                    Timer::after(500, function() use($playFds,$gameId, $gameRId, $gameNo, $gamePlays, $zjUid, $zjUserInfo, $useCards, $showList){
                        $pushArray = [ 'm' => BaseEnum::XYM_0X1A,  "data" => $showList ];
                        AuthCommon::pushFrontWeb($playFds, json_encode($pushArray));  
                        
                        //TODO 下注
                        $this->StartUserBetMuts($gameId, $gameRId, $gameNo, $gamePlays, $zjUid, $zjUserInfo, $useCards);
                    });
                } else {
                    AuthCommon::pushFrontWeb($playFds, json_encode([
                        'm' => $maxut,
                        "data" => null,
                        "msg" => $error
                    ])); // 推送相同内容
                }
            } else {
                BaseEnum::Logger('抢庄失败：PlayNiuNiu-(asycStartGame)-执行定时器计算抢庄失败 ---->>>Fail !!!!' . $gameNo, 'user-login');
            }
        });
    
    }

    
    /**
     * TODO 游戏玩家下注实现(下注把倍数存入内存中处理)
     * @param string $gameNo
     * @param int $roomId
     * @param boolean $params
     */
    public function asycPlayBets($roomId,$gameNo,$params = FALSE,& $errorNo = -1){
        $userId = isset($params['loginUid']) ? intval($params['loginUid']) : 0 ;
        $qmutil = isset($params['qmutil']) ? intval($params['qmutil']) : 1 ; //下注倍数
        if (!$roomId || empty($gameNo) || empty($params) || $userId <= 0) {
            return  FALSE;
        }
        $gameDetail = BaseComm::distMemoryRooms($gameNo, FALSE);        // 内存数据；
        $fjStatus = isset($gameDetail['fjStatus']) ? intval($gameDetail['fjStatus']) : 0;
        $users = isset($gameDetail['lists']) ? $gameDetail['lists'] : FALSE;
        
        
        //TODO 庄家ID
        $zjUid = 0 ;
        if(!empty($users)){
            foreach ( $users as $oneuu ){
                if(isset($oneuu['uid']) && intval($oneuu['uid'])>0 && isset($oneuu['mainflag']) && intval($oneuu['mainflag'])==1){
                    $zjUid = intval($oneuu['uid']) ;
                    break;
                }
            }
        }
        
        
        BaseEnum::Logger("游戏数据 : (asycPlayBets - [{$gameNo}])==>" . json_encode($gameDetail), 'user-login');
        if (!$gameDetail || !$users) {
            $errorNo = BaseEnum::MSG_0XFA;
            return FALSE;
        }
        
        //非下注状态；
        if(!$fjStatus || $fjStatus !== 1 ){
            $errorNo = BaseEnum::MSG_0XFD;
            return FALSE;
        }
        
        //庄家不能下注
        if ($zjUid == $userId) {
            $errorNo = BaseEnum::MSG_0XF2;
            return FALSE;;
        }
        
        
        
        $userSeat = array_column($users, "seatNo","uid");
        $userFds = array_column($users, "fd","seatNo");
        
        //TODO 实现下注；写入数据进到内存中（结构:lists为玩家信息，bets 下注列表）
        $userBets  = isset($gameDetail['bets']) ? $gameDetail['bets'] :  [];
        $pushObj = FALSE;
        foreach ($userSeat as $uuid => $seatIndex){
            if ($uuid && intval($uuid) === $userId) {
                $userBets[$seatIndex] = $qmutil ; //下注倍数存起放在内存中
                $pushObj = [ "seatNo" => $seatIndex, "mutdotts" => $qmutil, "p_fd" => $userId];
                break;
            }
        }
        
        //TODO 下注数据存起来
        $gameDetail['bets'] = $userBets;
        BaseComm::distMemoryRooms($gameNo,TRUE,$gameDetail);
        
        //TODO 下注信息
        $pushData = array( "m" => BaseEnum::XYM_0X19, "data" => $pushObj );
        BaseEnum::Logger("牛牛玩家下注成功 : (asycPlayBets - [{$gameNo}])==>" . json_encode($userBets), 'user-login');
        AuthCommon::pushFrontWeb($userFds, json_encode($pushData)); // 通知给所有玩家，谁在抢庄；
        
        //TODO  下注人员都满员启动提前结束定时器
        if(!empty($userBets) && sizeof($userBets) > 0 &&  sizeof($userBets) + 1 >= sizeof($userSeat)){
            PlayHelp::getInstance()->setCommTimerStop($gameNo,true);    //停掉定时器
            BaseEnum::Logger("所有玩家下注完成：========：(asycPlayBets) -- [ STOP ]-RoomId=". $gameNo ."  =======================设置为1,中断操作========>>>>>>", 'user-login');
        }
        
        return TRUE;
    }
    
    /**
     * TODO 进入下一步处理事件
     */
    public function doNextPlayBets($loginFd, $gameNo, $fjDetail = FALSE, $getUix = FALSE)
    {
        // 动画处理完触发；只能调用一次；触发动画倒计时；
        $gameId = isset($fjDetail['gameId']) ? intval($fjDetail['gameId']) : 0;
        $gameRid = isset($fjDetail['grid']) ? intval($fjDetail['grid']) : 0;
        $fjSta = isset($fjDetail['fjStatus']) ? intval($fjDetail['fjStatus']) : 0;
        $gamePlays = isset($fjDetail['lists']) ? $fjDetail['lists'] : FALSE; // 玩家列表；
        $playerFds = ! empty($gamePlays) ? array_column($gamePlays, "fd", "seatNo") : false; // 座位列表
        $Parms = [
            "gameNo" => $gameNo,
            "clientIds" => $playerFds,
            "status" => $fjSta
        ];
        $zjUserInfo = FALSE;
        if (! empty($gamePlays) && sizeof($gamePlays) >= 2) { // 查找庄家信息；
            foreach ($gamePlays as $oneuser) {
                if (isset($oneuser["uid"]) && intval($oneuser["uid"]) > 0 && isset($oneuser["mainflag"]) && intval($oneuser["mainflag"]) === 1) { // 庄家 mainflag = 1
                                                                                                                                                  // 庄家的倍数
                    $zjUserInfo = array(
                        "uid" => $oneuser["uid"],
                        "seatNo" => @$oneuser["seatNo"],
                        "bet_mutle" => isset($oneuser['mutdotts']) ? intval($oneuser['mutdotts']) : 0,
                        "main_flag" => 1 // 标识为庄主;
                    );
                    break;
                }
            }
        }
        // 标识为庄主;
        if (! $zjUserInfo || ! isset($zjUserInfo['uid']) || intval($zjUserInfo['uid']) <= 0)
        { 
            return FALSE;
        }
        //TODO 开启机器人自动下注功能
        $this->startRobotDoBets($gameId,$gameRid, $gameNo);
        
        $Long = $this->getConfigTimeLong('playbet_long') ?? 10;
        
        //TODO 下注倒计时开始
        $this->asycTimeStart($Long, $gameNo, $Parms, function () use ($gameNo, $gamePlays, $playerFds, $fjDetail, $zjUserInfo) {
            $zjUserid = isset($zjUserInfo["uid"]) ? intval($zjUserInfo["uid"]) : 0;
            $gameId = isset($fjDetail["gameId"]) ? intval($fjDetail["gameId"]) : 0;         // 游戏ID值；
            $gameRId = isset($fjDetail["grid"]) ? intval($fjDetail["grid"]) : 0;            // 房间ID值；
            $useCards = isset($fjDetail["frontCards"]) ? $fjDetail["frontCards"] : FALSE;   // 提前发的牌数据
            $firstStamp = isset($fjDetail["create"]) ? intval($fjDetail["create"]) : 0; 
            
            //TODO 获取起始时间间隔
            $countStamp = BaseEnum::getNowTimeLong($firstStamp);
            
            //房间内的数据
            $gameDetail = BaseComm::distMemoryRooms($gameNo,FALSE);
            $userBets  = isset($gameDetail['bets']) ? $gameDetail['bets'] :  [];            //下注的数据内容
            $gamePlays = isset($gameDetail['lists']) ? $gameDetail['lists'] : FALSE;        // 玩家列表；
            
            $allSeat = array_column($gamePlays, 'seatNo',"uid");
            
            $betLoggs=["开局第{$countStamp}秒  , 下注"];
            
            /**
             * TODO 下注==== 玩家下注TO DB
             * TODO 实际步骤：
             * 1、检查所有玩家是否有下注和下注倍数据
             * 2、把没有下注完的默认下1倍；并推给所有玩家
             */
            BaseEnum::Logger("执行定时器计算：PlayNiuNiu-(asycStartUserBetMuts)-=====[投注倍数]  ，去列队中给[{$gameNo}]所有玩家下单--（DB）-->>>" . json_encode($playerFds), 'user-login');
            BaseEnum::Logger('取内存数据：PlayNiuNiu-[asycStartUserBetMuts]--->>>' . json_encode($gameDetail), 'user-login');
            
            
            $userBetIds = [];
            $noticeUsers = [];
            //TODO 计算未下注的用户信息
            foreach ($allSeat as $uuid => $setid2){
                if (isset($uuid) && intval($uuid)>0 && intval($uuid) === $zjUserid) continue;
                $usrbetMul = isset($userBets[$setid2]) ? intval($userBets[$setid2]) : 0;        //下注倍数
                if($uuid > 0 && $usrbetMul <= 0 ){
                    $usrbetMul = 1;
                    $userBets[$setid2] = 1 ;
                    $noticeUsers[$uuid] = array("uid" =>$uuid,"seatNo" =>$setid2, "bet_mutle" => 1);        //未下注
                }
                $betLoggs[] = "{$setid2} 号位  下注{$usrbetMul} 倍";
                $userBetIds[] = $uuid;   //下注列表
            }
            
            
            //TODO 修改内存中游戏状态
            foreach ($gamePlays as $setiid => &$oneplay) {
                if (isset($userBets[$setiid]) && intval($userBets[$setiid]) > 0) {
                    $oneplay["mutdotts"] = intval($userBets[$setiid]);       // 修改下注倍数
                    $oneplay["pstatus"] = CardEnum::GAME_STEP_2;             // 修改下注成功；
                }
            }
            $gameDetail['bets'] = $userBets;
            $gameDetail['fjStatus'] = CardEnum::GAME_STEP_2;                // 下注成功
            $gameDetail['lists'] = $gamePlays;
            BaseComm::distMemoryRooms($gameNo,TRUE,$gameDetail);            //TODO 存储游戏数据
            BaseEnum::wirteRedisData(SysEnum::ONLINE_ROOMS_LIST_KEY . $gameNo, json_encode($gameDetail));
            
            $error = -1;
            
            //TODO 所有玩家订单入库处理： (闲家下注)
            $savedata = array("ptype"=> 2 ,"userId" => implode(',', $userBetIds) ,"data"=> $userBets);
            $dbFlag = GameCommon::updateRoomsPInfo($gameNo,$savedata,$gamePlays,$error);  //下注成功；
            
            if ($dbFlag !== TRUE) {
                BaseEnum::Logger("闲家下注失败：(下注) ->>> ERROR -- {$error}，  未下注玩家：" . json_encode($noticeUsers), 'user-login');
                $userBetInfo[$zjUserid] = $zjUserInfo; // 合并庄家的ID
                return FALSE;
            }
            
            BaseComm::gameLogger($gameNo,$betLoggs);       //下注日志    
            
            // TODO 单个数据推到前端；
            $allFds = array_column($gamePlays, "fd","seatNo");
            if (! empty($noticeUsers)) {
                $noticeUsers = array_column($noticeUsers, null, "seatNo");  // 通知给所有玩家，下注倍数；
                foreach ($noticeUsers as $siteid => $oneline) {
                    $oneInfo = [ "seatNo" => $siteid, "mutdotts" => isset($oneline['bet_mutle']) ? intval($oneline['bet_mutle']) : 1, "self" => 1];
                    $pushdata = ['m' => BaseEnum::XYM_0X19, "data" => $oneInfo];
                    AuthCommon::pushFrontWeb($allFds, json_encode($pushdata));   // 推送未下注玩家默认1倍
                }
            }
            
            // TODO 启动发牌[XYM_0X30]
            $Long = $this->getConfigTimeLong('waitcard_long') ?? 2;
            Timer::after($Long* 1000, function() use($gameId, $gameRId, $gameNo,$allFds,$useCards){
                
                BaseEnum::Logger("启动发牌：(只发每个人的手牌) ->>> Start，  INFO：" . json_encode([$gameId, $gameRId, $gameNo]), 'user-login');
                $this->StartCards($gameId, $gameRId, $gameNo, $allFds, $useCards);
            });
        });
    }

    /**
     * TODO 掉线重连操作
     *
     * @param int $userid
     * @param int $loginFd
     * @param string $newGameNo
     */
    public function reConnectPlay($userid, $loginFd, $gameNo = FALSE)
    {
        if (! $gameNo || $userid <= 0)
            return FALSE; // 玩家最新游戏ID对应的号
        $fjData = BaseComm::distMemoryRooms($gameNo, FALSE);
        $gameId = isset($fjData['gameId']) ? intval($fjData['gameId']) : 0;
        $gameStatus = isset($fjData['fjStatus']) ? intval($fjData['fjStatus']) : 0;
        $gamePlays = isset($fjData['lists']) ? $fjData['lists'] : FALSE;
        // 只要游戏没有结束就换掉用户的客户端FD值，否则推送出错；
        if (! empty($gamePlays)) {
            foreach ($gamePlays as $r => $oneline) {
                if (isset($oneline['uid']) && intval($oneline['uid']) === $userid) {
                    $gamePlays[$r]['fd'] = $loginFd; // 当前登录用户的客户FD值；
                }
            }
            $fjData['lists'] = $gamePlays; // 修改后的数据重新给值
            BaseComm::distMemoryRooms($gameNo, TRUE, $fjData); // 保存数据
                                                               // 游戏数据恢复
            BaseEnum::Logger("掉线重连：PlayNiuNiu --> reConnectPlay-[{$userid},$gameId ,$gameNo]-------gameData-->" . json_encode($fjData), 'user-login');
            // 查询当前游戏状态：
            if (! empty($fjData) && $gameStatus >= 0 && in_array($gameStatus, [
                0,
                1,
                2,
                3,
                4,
                5
            ])) { // 大于1 都是游戏中；
                $steps = BaseEnum::GAME_STEP;
                $stepName = isset($steps[$gameStatus]) ? $steps[$gameStatus] : "-";
                BaseEnum::Logger("掉线重连：PlayNiuNiu --> 当前状态：-[{$stepName},$gameNo]-------status-->" . $gameStatus, 'user-login');
                // 显示游戏中的数据结构：定义新的结构；
                $returnData = array(
                    "gameNo" => $gameNo,
                    "step" => $stepName,
                    "status" => $gameStatus,
                    "lists" => []
                );
                // 恢复游戏数据： 只处理以下几张情况；
                // 1、抢庄中---谁庄过庄（标识出来），
                // 2、下注中(谁是庄家标识出来，)
                // 3、结算中(庄家，下注倍数，当前玩家牌值内容)
                $gameResult = $this->reBackData($userid, $gameStatus, $gameNo, $gamePlays);
                BaseEnum::Logger("掉线重连：PlayNiuNiu --> 数据恢复：-[{$gameNo}]-------status-->" . json_encode($gameResult), 'user-login');
                if (! $gameResult) {
                    return FALSE;
                }
                // 显示每个玩家的数据内容；
                $returnData["lists"] = $gameResult;
                return $returnData;
            }
        }
        return FALSE;
    }

    /**
     * TODO 恢复游戏数据；
     * TODO 处理以下几张情况；
     * 1、抢庄中---谁庄过庄（标识出来），
     * 2、下注中(谁是庄家标识出来，)
     * 3、结算中(庄家，下注倍数，当前玩家牌值内容)
     *
     *  TODO 附状态值：0 => '匹配', 1 => '抢庄', 2 => '下注', 3 => '发牌', 4 => '拼牌', 5 => '结算',
     *
     * @param int $gameStatus
     * @param array $gamePlays
     */
    protected static function reBackData($loginUid, $gameStatus, $gameNo = FALSE, $gamePlays = FALSE)
    {
        if (! $loginUid || ! $gamePlays || ! $gameNo)
            return false;
        $redisobj = new RedisTools();
        $redisobj->select(0);
        $palyUser = $gamePlays;
        foreach ($palyUser as & $oneus) {
            unset($oneus['fd']);
            unset($oneus['agentId']);
            unset($oneus['betmoney']);
        }
        // 匹配成功 开始抢庄 ===> 谁有抢庄
        if ($gameStatus === CardEnum::GAME_STEP_0) {
            $rediskey = SysEnum::ONLINE_BDATA_KEY . "_MUT"; // 查询下注数据；
            $betdata = $redisobj->hGet($rediskey, $gameNo);
            $betArray = ! empty($betdata) ? json_decode($betdata, TRUE) : FALSE;
            foreach ($palyUser as $sid => $oneuser) {
                $palyUser[$sid]['pstatus'] = '玩家抢庄';
            }
        } //TODO 抢庄完成 开始下注 ===> 标出谁是庄家,闲家下注倍数；
        else if ($gameStatus === CardEnum::GAME_STEP_1) {
            $rediskey = SysEnum::ONLINE_BDATA_KEY . "_MUT"; // 查询下注数据；
            $betdata = $redisobj->hGet($rediskey, $gameNo);
            $betArray = ! empty($betdata) ? json_decode($betdata, TRUE) : FALSE;
            foreach ($palyUser as $sid => $oneuser) {
                $palyUser[$sid]['pstatus'] = '闲家下注';
                if (isset($oneuser['mainflag']) && intval($oneuser['mainflag']) == 1) { // 定庄：uid,
                    $palyUser[$sid]['text'] = '庄';
                    $palyUser[$sid]['pstatus'] = '等待闲家下注';
                } else {
                    $palyUser[$sid]['text'] = '闲';
                    $palyUser[$sid]['mutdotts'] = isset($betArray[$sid]) ? intval($betArray[$sid]) : 0; // 闲家下注倍数
                    $palyUser[$sid]['max_mutle'] = 15; // 最大下注倍数；
                }
            }
        } //TODO 下注完成 开始发牌 拼牌 ===> 开始发牌（显示下注倍数）
        else if (in_array($gameStatus, [
            CardEnum::GAME_STEP_2,
            CardEnum::GAME_STEP_3
        ], true)) {
            $cards = BaseComm::distMemoryCards($gameNo, FALSE); // 全局变量获取手牌数据（座位号seatNo=> [11,23,233]）
            foreach ($palyUser as $sid => $oneuser) {
                $palyUser[$sid]['pstatus'] = ($gameStatus == 3) ? "拼牌中" : '发牌中';
                $palyUser[$sid]['ifcards'] = 1; // 已发牌
                $palyUser[$sid]['cards'] = false; // 已发牌
                if (isset($oneuser['uid']) && intval($oneuser['uid']) == $loginUid) { // 显示发给我的手牌；
                    $palyUser[$sid]['cards'] = isset($cards[$sid]) ? $cards[$sid] : [];
                }
            }
        } //TODO 拼牌完成 摊牌 ===> 结算数据（显示玩家最后的结算数据）
        else if (in_array($gameStatus, [
            CardEnum::GAME_STEP_4,
            CardEnum::GAME_STEP_5
        ], true)) {
            $cards = BaseComm::distMemoryCards($gameNo, FALSE); // 全局变量获取手牌数据（座位号seatNo=> [11,23,233]）
            foreach ($palyUser as $sid => $oneuser) {
                $palyUser[$sid]['pstatus'] = "结算中";
                $palyUser[$sid]['ifcards'] = 1; // 已发牌
                $palyUser[$sid]['cards'] = isset($cards[$sid]) ? $cards[$sid] : [];

                //TODO 显示结算所的金额数据；
                $palyUser[$sid]['lastmoney'] = 0.01;
            }
        }
        return $palyUser;
    }

    /**
     * TODO 异步监控房间内下注
     * asycStartUserBetMuts
     */
    protected function StartUserBetMuts($gameId, $gameRId, $gameNo, $players = false, $zjUserid = 0, $zjUserInfo = false, $useCards = FALSE)
    {
        BaseEnum::Logger('闲家异步统计：PlayNiuNiu-(StartUserBetMuts)-统计 ---->>>>>>>>>>>>> 待完善:' . $gameNo, 'user-login');
        // 8秒动画效果；
        $Long = $this->getConfigTimeLong('waitbet_long') ?? 5;
        Timer::after($Long * 1000, function () use ($gameNo, $gameId) {
            $fjDetail = BaseComm::distMemoryRooms($gameNo); // 房间内的信息；

            // 查看当前状态是否启动；
            if (isset($fjDetail["doNext"]) && intval($fjDetail["doNext"]) > 0) { // 已经处理过了；
                BaseEnum::Logger('异步自动下注执行：PlayNiuNiu-(StartUserBetMuts)-[检查到doNext=1 已经执行处理过，不再处理]--->>>' . json_encode($fjDetail), 'user-login');
            } else {
                $fjDetail["doNext"] = 1;
                BaseComm::distMemoryRooms($gameNo, true, $fjDetail); // 保存标识不允许再次发起
                $this->doNextPlayBets(null, $gameNo, $fjDetail, 0);
            }
        });
    }

    /**
     * TODO 开始发牌操作；
     * @param array $paramList
     */
    protected function StartCards($gameId, $gameRId, $room_No, $playerFds = false)
    {
        $playerCards = [];
        $gameDatas = BaseComm::distMemoryRooms($room_No, FALSE);
        $useCards = BaseComm::distMemoryCards($room_No,FALSE); //数据：[[4,10],[8,4],[4,8],[7,1]]]
        $gamePlays = $gameDatas['lists'] ?? FALSE;
        $firstStamp = isset($gameDatas["create"]) ? intval($gameDatas["create"]) : 0;
        BaseEnum::Logger('取内存数据：PlayNiuNiu--[StartCards]--->>>' . json_encode($gameDatas), 'user-login');
        if(empty($gamePlays)){
            BaseEnum::Logger("游戏数据异常推送：PlayNiuNiu-(StartCards)-{$gameRId}==>{$room_No}|data : " . json_encode($gamePlays), 'user-login');
            return FALSE;
        }
        $playerFds = array_column($gamePlays, "fd", "seatNo"); // 使用最新数据
        $playerMoneys = array_column($gamePlays, "points", "seatNo"); // 使用最新数据
        
        //TODO 启动发牌；======> 操作代码 ： XYM_0X19；
        $userAllCards = [];
        if (!empty($useCards)) {
            foreach ($useCards as $vs => $onecard){
                $userAllCards[$vs] = ['seatNo'=> $vs,"cards" =>$onecard];
            }
        }else {
            BaseEnum::Logger("重启生成牌：PlayNiuNiu-(StartCards)->{$gameRId}=>{$room_No}|  --> allUserPoints : " . json_encode($playerMoneys), 'user-login');
            $userAllCards = GameCommon::createCardList($gameId, $room_No, ["roomId"=>$gameRId]);
        } 
        BaseEnum::Logger("GET发牌数据： ===>  GetCards：" . json_encode($userAllCards), 'user-login');
        
        //TODO  发牌
        if (isset($userAllCards) && ! empty($userAllCards)) {
            $gameDatas['fjStatus'] = CardEnum::GAME_STEP_3; // 修改为发牌成功；
            foreach ($gamePlays as & $ofg) {
                if(!empty($ofg) && isset($ofg['uid']))$ofg["pstatus"] = CardEnum::GAME_STEP_3;
            }
            $gameDatas['lists'] = $gamePlays;
            BaseComm::distMemoryRooms($room_No, TRUE, $gameDatas); // 保存用户数据
            BaseEnum::wirteRedisData(SysEnum::ONLINE_ROOMS_LIST_KEY . $room_No, json_encode($gameDatas)); // 缓存；
            $long = BaseEnum::getNowTimeLong($firstStamp);
            BaseComm::gameLogger($room_No,"开局第{$long}秒 , 发玩家手牌");
            
            
            //TODO 测试数据牌值 调试数据
            /* $tempCards = [
                [3,4,8,1,58],
                [24,41,53,61,60],
                [50,56,29,52,42],
                [21,18,19,54,36]
            ]; */
            
            //TODO 分发给每个用户(发牌代号：BaseEnum::XYM_0X1C )
            foreach ($userAllCards as $pushOneMsg) {
                $fullC = "";
                $seatId = isset($pushOneMsg['seatNo']) ? intval($pushOneMsg['seatNo']) : 0;
                $oneCards = $pushOneMsg['cards'] ?? FALSE;
                
                //DEBUG 调试数据  $oneCards = $tempCards[$seatId] ?? $oneCards;
                $curUfd = isset($playerFds[$seatId]) ? intval($playerFds[$seatId]) : 0;
                $curUpoint = isset($playerMoneys[$seatId]) ? floatval($playerMoneys[$seatId]) : 0;
                if ($seatId !== false && $seatId >= 0 &&  !empty($oneCards)) {
                    //TODO  看牌抢庄牛 === > 发3张牌（后两张）；
                    if ($gameId === 201 && sizeof($oneCards) == 5) { 
                        $fullC = implode(',', $oneCards);
                        $oneCards = array_slice($oneCards, 3); // 取后两张
                    }
                    $pushWjInfo = [
                        "seatNo" => $seatId,
                        "cards" => $oneCards,
                        "step" => 1,
                        "userPoints"=> $curUpoint,
                        "remark" => $fullC
                    ];
                    if ($curUfd > 0) {
                        $pushOneObj = [ 'm' => BaseEnum::XYM_0X17, "data" => $pushWjInfo];
                        AuthCommon::pushFrontWeb($curUfd, json_encode($pushOneObj));            // 发牌推送取实时的FD
                    }
                    $playerCards[$seatId] = $pushWjInfo;
                }
            }
            
            //TODO 启动牌值计算数据；====> 看牌过程
            $this->asycWaitDealCenter($gameRId, $room_No, $playerCards, $playerFds);
        } else {
            BaseEnum::Logger('发牌失败：PlayNiuNiu-(asycStartCards)-发牌；======>   失败 Fail !! -->  ' . $room_No, 'user-login');
        }
    }

    /**
     * TODO 看牌结算处理；
     * @param string $room_No
     * @param array $useCards
     * @param boolean $playerFds
     */
    protected function asycWaitDealCenter($gameRId, $gameNo, $useCards, $playerFds = FALSE)
    {
        $Long = $this->getConfigTimeLong('waitdeal_long') ?? 5;
        $gameAllData = BaseComm::distMemoryRooms($gameNo, FALSE); // 内存数据；
        $fjSta = isset($gameAllData['fjStatus']) ? intval($gameAllData['fjStatus']) : 0;
        $gamePlays = isset($gameAllData['lists']) ? $gameAllData['lists'] : FALSE; // 玩家列表；
        $playerFds = ! empty($gamePlays) ? array_column($gamePlays, "fd", "seatNo") : false; // 座位列表
        $Parms = [
            "gameNo" => $gameNo,
            "clientIds" => $playerFds,
            "status" => $fjSta
        ];
        $this->asycTimeStart($Long, $gameNo, $Parms, function () use ($gameNo, $playerFds) {
            $fjDetail = BaseComm::distMemoryRooms($gameNo, FALSE);
            $betList = isset($fjDetail['lists']) ? $fjDetail['lists'] : false;
            $firstStamp = isset($fjDetail["create"]) ? intval($fjDetail["create"]) : 0;
            $long = BaseEnum::getNowTimeLong($firstStamp);
            BaseComm::gameLogger($gameNo,"开局第{$long}秒 , 玩家拼牌");
            
            //TODO 下边玩家有几个还没推送拼牌的操作；
            $notPinfds = [];
            if (! empty($betList)) {
                
                //TODO  FD防变动
                $playerFds = ! empty($betList) ? array_column($betList, "fd", "seatNo") : false; // 使用最新数据
                BaseEnum::Logger('拼牌数据查找：PlayNiuNiu-(asycWaitDealCenter)-是否有未拼牌的：[asycWaitDealCenter]--->>>' . json_encode($betList), 'user-login');
                foreach ($betList as $siteid => $onep) {
                    if (isset($onep['pstatus']) && intval($onep['pstatus']) !== CardEnum::GAME_STEP_4 && isset($onep['fd'])) { // 还没拼牌玩家
                        $notPinfds[] = $siteid;
                    }
                }
                if (! empty($notPinfds)) {
                    foreach ($notPinfds as $seatNo) {
                        $data = [
                            "seatNo" => $seatNo,
                            "status" => 1,
                            "text" => "拼牌完成"
                        ]; 
                        //TODO 推送给所有玩家，拼牌完成；
                        $pushData = [  'm' => BaseEnum::XYM_0X21,  "data" => $data ];
                        AuthCommon::pushFrontWeb($playerFds, json_encode($pushData)); // 推送
                    }
                }
            }
        });

        //TODO 抢庄====倒计时15秒钟;
        Timer::after($Long * 1000, function () use ($gameRId, $useCards, $gameNo, $playerFds) {
            $allCards = [];
            $allUserIds = [];
            $gameDoRealNum = 0 ;
            // 房间信息
            $roomDetail = BaseComm::GameRoomDetail($gameRId);
            $gameId = $roomDetail["gameId"] ?? 0;
            // 棋牌内容：
            $fjDetail = BaseComm::distMemoryRooms($gameNo, FALSE);
            $userbetList = $fjDetail['lists'] ?? FALSE;
            $userBets  = isset($fjDetail['bets']) ? $fjDetail['bets'] :  [];    //下注倍数
            
            $firstStamp = isset($fjDetail["create"]) ? intval($fjDetail["create"]) : 0;
            if (! empty($fjDetail) && !empty($userbetList)) {
                $playerFds = array_column($userbetList, "fd", "seatNo");    // FD防变动
                $allUserIds = array_column($userbetList, "uid");            // 所有UID
            }
            BaseEnum::Logger("牛牛结算处理：PlayNiuNiu-(asycWaitDealCenter)-[roomid={$gameRId}]；======> Start ....-->UserList=" . json_encode($userbetList), 'user-login');
            BaseEnum::Logger("牛牛结算：PlayNiuNiu-(asycWaitDealCenter)-[roomid={$gameRId}]；======> getRoomInfo=" . json_encode($roomDetail), 'user-login');
            $uSeatList = ! empty($userbetList) ? array_column($userbetList, null, 'seatNo') : false;

            //TODO 比牌数据操作；$useCards
            $zjObject = false;
            $realUserNotices = []; //真实玩家的结算数据
            
            if (! empty($useCards) && ! empty($uSeatList) && ! empty($userbetList)) {
                foreach ($useCards as $seatId => $cardinfo) {
                    $allCards[$seatId] = isset($cardinfo["cards"]) ? $cardinfo["cards"] : false;    // 取牌数据
                    $tempMut = $userBets[$seatId] ?? 0;                                             //下注倍数
                    $useFo = isset($uSeatList[$seatId]) ? $uSeatList[$seatId] : false;
                    $useBetMuts = (isset($useFo['mutdotts']) && intval($useFo['mutdotts']) > 0 ) ? intval($useFo['mutdotts']) : 0;
                    if ($tempMut>0 && $tempMut != $useBetMuts) $useBetMuts = intval($tempMut); //优先内存数据
                    $useCards[$seatId]["userId"] = isset($useFo['uid']) ? intval($useFo['uid']) : 0;
                    $useCards[$seatId]["mainFlag"] = isset($useFo['mainflag']) ? intval($useFo['mainflag']) : 0; // 庄家标识
                    $useCards[$seatId]["mainMutle"] = $useBetMuts > 0 ? $useBetMuts : 1 ; // 下注或抢庄的倍数关系
                    if ($useCards[$seatId]["mainFlag"] === 1) {
                        $zjObject = $useCards[$seatId];
                        unset($useCards[$seatId]);      //TODO 处理一下倍数关系统；至少1倍 mainMutle
                    }
                }

                //TODO 结算处理；
                BaseEnum::Logger('牛牛庄家：PlayNiuNiu-(asycWaitDealCenter)===-->' . json_encode($zjObject), 'user-login');
                BaseEnum::Logger('牛牛闲家：PlayNiuNiu-(asycWaitDealCenter)===-->' . json_encode($useCards), 'user-login');
                
                //TODO 结算玩家的计算；把庄家跟闲家进行一一对比；
                $resultList = GameCommon::dealPlayMoney($gameRId, $gameNo, $zjObject, $useCards, $roomDetail,$realUserNotices);  // 处理结果；谁赢钱；

                //TODO 推送：所有牌数据和输赢；
                if (! empty($resultList) && sizeof($resultList) > 1) {
                    $gameDoRealNum = sizeof($resultList); //真实人数统计
                    BaseEnum::Logger('牛牛结算处理：PlayNiuNiu-(asycWaitDealCenter)-；======> SUCCESS ....-->  ' . json_encode($resultList), 'user-login');
                    $fjDetail['fjStatus'] = CardEnum::GAME_STEP_5;
                    if (!empty($userbetList)) {
                        foreach ($userbetList as &$fg) {
                            if(!empty($fg) && isset($fg["uid"])) $fg["pstatus"] = CardEnum::GAME_STEP_5;
                        }
                        $fjDetail['lists'] = $userbetList;
                    }
                    //TODO 获取起始时间间隔
                    $countStamp = BaseEnum::getNowTimeLong($firstStamp);
                    $gameLoggs= ["开局第{$countStamp}秒 , 结算"];
                    BaseComm::distMemoryRooms($gameNo, TRUE, $fjDetail); // 保存用户数据
                    BaseEnum::wirteRedisData(SysEnum::ONLINE_ROOMS_LIST_KEY . $gameNo, json_encode($fjDetail)); // 缓存；
                    foreach ($resultList as $setid => $res) {
                        $cellfee = $res['cellScore'] ??0;
                        $chagefee = $res['changeMoney'] ?? 0;
                        $cards2 = isset($allCards[$setid]) ? $allCards[$setid] : [];   // 取牌数据
                        $gameLoggs[] = "{$setid} 号位  本局输赢:{$chagefee},下注分：{$cellfee},牌值：".json_encode($cards2);
                        if(!isset($res['cards']) || empty($res['cards'])){
                            $resultList[$setid]["cards"] = $cards2;
                        }
                    }
                    
                    //TODO 前端结算
                    AuthCommon::pushFrontWeb($playerFds, json_encode([
                        'm' => BaseEnum::XYM_0X1C,
                        "data" => array_values($resultList)
                    ])); 
                    
                    //TODO 在线玩家数据（游戏结束，在线人数减少）
                    BaseComm::changeOnLines($gameId,$gameDoRealNum,1);
                    BaseComm::distLastGameNoListRedis($allUserIds,TRUE,FALSE);          //TODO 清掉最新局
                    PlayCommon::doBackFreeRobot($gameId,$allUserIds);                   //TODO 清掉所有机器人玩家被占用的缓存
                    PlayCommon::gameNoticeSaveCache($gameId,$gameNo,$realUserNotices);  //TODO 加入游戏通知列表
                    BaseComm::gameLogger($gameNo,$gameLoggs,["finish"=>TRUE]);          //TODO 加入对局日志
                    return TRUE;
                } else {
                    BaseEnum::Logger('牛牛结算处理：结算失败 ！！！！PlayNiuNiu-(asycWaitDealCenter)-；======>  ' . print_r($zjObject, true), 'user-login');
                    return false;
                }
            }
        });
    }
}

?>